#!/usr/bin/perl
# Author:  John Johnson (john.g.johnson@jpmchase.com)
# 
# Includes "borrowed" code from ldaperrcnt.pl found the Novell
# Cool Solutions website:
#
# 		 http://www.novell.com/coolsolutions/tools/1844.html
#
# Intended for use on logs from eDirectory 8.7.3.1+
# Tested to run on Solaris, Linux, and Win32
#
# Assumes that package HTTP/Date.pm is installed or in execution directory.
#
# Reads specified log file to organize threads to determine start/end time
# of a given operation, and calculate elapsed time.  Provides bind, search
# modify, and total operation counts.  Also breaks down and translates result
# codes into descriptive messages.  Finally, logs operations that take >1
# second to execute, or that have "abnormal" result codes
# (normal = 0 5 6 49 65).
#
# Input ndstrace log expects ndstrace filters BASE, TAGS, TIME, and LDAP
# to be set.  Also, LDAP Server Screen options, all except "Packet Decode"
# should be enabled.
#
#
# Input:		 Win32:  specified ndstrace.log file
#		 		 UNIX:		 start an ndstrace pipe to read real-time,
#		 		 		   or specified ndstrace.log file
#
# Parameters: 
#        -a Outputs all operations (usually logs only those deemed interesting)
#		   -s Silent mode (will not print results to screen)
#		   -f Read ndstrace log from a file
#
# Output:	 Summary to screen (refreshes every $refresh seconds)
#		 		 Summary to file ldapsummary.log or /tmp/ldapsummary.log depending on execution platform
#		 		 Operations that are calculated to execute longer than >1s    
#                  or that result an error are logged.  
#     Details OVERWRITTEN in ldapoptime.log (if using input file) or APPENDED to
#		 		   /var/opt/novell/eDirectory/log/ldapoperations.log (if processing live ndstrace).
#
# Useful reference for date/time manipulation:
#		 http://www.perldoc.com/perl5.6/lib/HTTP/Date.html
#
# ===============================================================================
# Mofifications 
# 2012-11-24 Minor modificaitons and verifications against eDirectory 8.8.7 jim@willeke.com
# ===============================================================================

use Getopt::Std;
use HTTP::Date qw(str2time time2iso);		 # time2iso is not available by default

# ===============================================================================
#		 - - - - - - - - - - - - - - MAIN ROUTINE  - - - - - - - - - - - - - -	#
# ===============================================================================

# Global Data Area
@allmsg = (
'SUCCESS',
'OPERATIONS ERROR',
'PROTOCOL ERROR',
'TIMELIMIT EXCEEDED',
'SIZELIMIT EXCEEDED',
'COMPARE FALSE',
'COMPARE TRUE',
'STRONG AUTH NOT SUPPORTED',
'STRONG AUTH REQUIRED',
'PARTIAL RESULTS',
'REFERRAL RECEIVED',
'ADMINLIMIT EXCEEDED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NO SUCH ATTRIBUTE',
'UNDEFINED TYPE',
'INAPPROPRIATE MATCHING',
'CONSTRAINT VIOLATION',
'TYPE OR VALUE EXISTS',
'INVALID SYNTAX',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NO SUCH OBJECT',
'ALIAS PROBLEM',
'INVALID DN SYNTAX',
'IS LEAF',
'ALIAS DEREF PROBLEM',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'INAPPROPRIATE AUTH',
'INVALID CREDENTIALS',
'INSUFFICIENT ACCESS',
'BUSY',
'UNAVAILABLE',
'UNWILLING TO PERFORM',
'LOOP DETECT',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NAMING VIOLATION',
'OBJECT CLASS VIOLATION',
'NOT ALLOWED ON NONLEAF',
'NOT ALLOWED ON RDN',
'ALREADY EXISTS',
'NO OBJECT CLASS MODS',
'RESULTS TOO LARGE',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'NOT USED',
'OTHER',
'SERVER DOWN',
'LOCAL ERROR',
'BER ENCODING ERROR',
'BER DECODING ERROR',
'TIMEOUT',
'AUTH UNKNOWN',
'FILTER ERROR',
'USER CANCELLED',
'PARAM ERROR',
'NO MEMORY',
'CONNECT ERROR',
'NOT SUPPORTED',
'CONTROL NOT FOUND',
'NO RESULTS RETURNED',
'MORE RESULTS TO RETURN',
'CLIENT LOOP',
'REFERRAL LIMIT EXCEEDED');
@allerr = (0) x 98;
%ops = (); %conns = ();
$startlog=0; $endlog=0;
$refreshTimer=0;
$refresh=5; # Seconds between screen refresh and writing the summary log
$refreshCounter=0;
$timeout=240; # How many seconds an operation will stay in memory before it is logged & dropped
$cleanupCount=12; # Determines how many $refresh intervals should elapse between each cleanOps run
$toomanyresults=50; # Threshold for what is considered "too many results" for an op; causes op to be logged
# Give positional elements of counts array friendly names
$opstotal=0;
$ops10ms=1;
$ops100ms=2;
$ops250ms=3;
$ops500ms=4;
$ops1000ms=5;
$ops2000ms=6;
$opsgr2000ms=7;
$opstoomanyresults=8;
$opsdropped=9;
$opslogged=10;
# Intialize the total counts
foreach $i (0..10) { $opscount{'Total'}[$i]=0; }
# Give positional elements of ops array friendly names
$starttime=0;
$thread=1;
$connip=2;
$socket=3;
$opno=4;
$connection=5;
$optype=6;
$result=7;
$optime=8;
$resultcount=9;
$msgs=10;
$opthrd_last = "unknown"; $opid_last = "unknown"; $connid_last = "unknown"; $sock_last = "unknown";
$timeval = 0; $timeval_last = 0;

# Get command line parameters
getopts('asf:');
if ( $opt_a ) { $outputdef=$opt_a; } else { $outputdef=0; }
if ( $opt_s ) { $silent=$opt_s; } else { $silent=0; }
# Setup everything based on environment and parameters specified
if ( $ENV{'OS'} =~ /Windows/ ) {
		 $clear="cls";
		 $LOGfile = $opt_f; $SUMMARYfile = $opt_f; $DEBUGfile = $opt_f;
		 $LOGfile =~ s/\.log|\.txt/\.csv/g;
		 $SUMMARYfile =~ s/\.log|\.txt/\.summary\.txt/g;
		 $DEBUGfile =~ s/\.log|\.txt/\.debug\.txt/g;
		 if ( $opt_f ) {
		 		 open (NDSTRACE, $opt_f)
		 		 		 or die "\nCan't open $opt_f\n";
		 }
		 else { die "\nMust specify input file:  -f filename\n"; }
		 $ndstrace=0;
		 open(LOG, "> $LOGfile")
		 		 or die "\nCan't open $LOGfile\n";
		 open(DEBUG, "> $DEBUGfile")
		 		 or die "\nCan't open $DEBUGfile\n";
		 # Print a header line in the log
		 print LOG "starttime;connip:sock;connid;optype;result;optime;resultcount;msgs\n";
}
elsif ( `uname` =~ /SunOS|Linux/ ) {
		 $clear="clear";
		 if ( $opt_f ) {
		 		 $LOGfile = $opt_f; $SUMMARYfile = $opt_f; $DEBUGfile = $opt_f;
		 		 $LOGfile =~ s/\.log|\.txt/\.csv/g;
		 		 $SUMMARYfile =~ s/\.log|\.txt/\.summary\.txt/g;
		 		 $DEBUGfile =~ s/\.log|\.txt/\.debug\.txt/g;
		 		 open(LOG, "> $LOGfile")
		 		 		 or die "\nCan't open $LOGfile\n";
		 		 open(DEBUG, "> $DEBUGfile")
		 		 		 or die "\nCan't open $DEBUGfile\n";
		 		 # Print a header line in the log
		 		 print LOG "starttime;connip:sock;connid;optype;result;optime;resultcount;msgs\n";
		 		 open (NDSTRACE, $opt_f)
		 		 		 or die "\nCan't open $opt_f\n";
		 		 $ndstrace=0;
		 }
		 else {
		 		 $LOGfile = "/var/opt/novell/eDirectory/log/ldapoperations.log";
		 		 $SUMMARYfile = "/tmp/ldapoperations.log";
		 		 $DEBUGfile = "/tmp/ldapoperationsdebug.log";
		 		 open(LOG, ">> $LOGfile")
		 		 		 or die "\nCan't open $LOGfile\n";
		 		 open(DEBUG, "> $DEBUGfile")
		 		 		 or die "\nCan't open $DEBUGfile\n";
		 		 open NDSTRACE, "ndstrace -l 2>/dev/null |"
        		 		 		 or die "\nCan't fork: $!\n";
        		 $ndstrace=1;
        		 # Turn on the needed ndstrace filters
		 		 select(undef, undef, undef, 2); 
		 		 `ndstrace -c "set dstrace=nodebug; ndstrace TAGS BASE TIME LDAP" >/dev/null 2>&1`;
		 }
}
else { die "\nUnrecognized environment -- read the script.\n"; }

# Start the main loop to process log data
while ( $inrec = <NDSTRACE> ) {
		 chomp $inrec;
		 # This mess is to handle what I can only assume is an ndstrace bug that permits
		 # the contents of two separate trace lines to appear on a single line. *sigh*
		 @lines=split(/(?:(\d*\s*LDAP:.*?|\t))/, $inrec);
#		 foreach $line (@lines) {
#		 		 printf "line=|$line|\n";
#		 }
		 $segcount=1;
		 foreach $seg (@lines) {
		 		 # Strip ANSI color codes and DOS CR characters
		 		 $seg =~ s/\x1B.*?m|\x0D//g;
		 		 # The split puts the 'seperator' and the rest of the line in different array elements;
		 		 # they are in pairs, so put the odd and even values together.
		 		 if ( $seg ) {
		 		 		 #printf "segcount=$segcount seg=|$seg| ";
		 		 		 if ( $segcount%2 ) {
		 		 		 		 $line = $seg; $segcount += 1;
		 		 		 }
		 		 		 else {
		 		 		 		 $line = $line . $seg; $segcount += 1;
		 		 		 		 #printf "line2parse=|$line|\n";
		 		 		 		 &parseLine($line);

		 		 		 }
		 		 }
		 }
		 # Log the "corrupted" lines found in the trace file
		 if ( (($inrec =~ /LDAP.*\t/) && ($segcount > 5)) || (($inrec !~ /\t/) && ($segcount > 3)) ) {
		 		 printf DEBUG "Corrupt input line=|$inrec|\n";
		 }
}

&logSummary;
&displaySummary;
&cleanOps;
&debugOps;

close(NDSTRACE);
close(LOG);
close(DEBUG);

# ===============================================================================
#		 - - - - - - - - - - - - - - SUB-ROUTINES  - - - - - - - - - - - - - - 	#
# ===============================================================================

# ===============================================================================
# ctrl_c_handler perform actions on ctrl-c
# ===============================================================================
sub ctrl_c_handler  {
        if ( $ndstrace ) { `ndstrace -u`; }
		 &logSummary;
		 &displaySummary;
		 &cleanOps;
		 &debugOps;
        close(NDSTRACE);
		 close(LOG);
		 die "\nUser aborted.\n";
}
$SIG {"INT"} = \&ctrl_c_handler;  # "INT" indicates  "Interrupt" 
                                  # signal.
# ===============================================================================
# displaySummary
# ===============================================================================
sub displaySummary
{
		 if ( $silent == 0 ) {
		 		 system "$clear";
		 		 printf ("\n\n");
		 		 printf ("     Operation:  Count:<=0.010:<=0.250:<=1.000:<=2.000: >2.000: logged:dropped\n");
		 		 while( $Key = each(%opscount) ) {
		 		 		 printf ("%14s:%7d:%7d:%7d:%7d:%7d:%7d:%7d:%7d\n",
		 		 		 		 		 		 $Key,
		 		 		 		 		 		 $opscount{$Key}[$opstotal],
		 		 		 		 		 		 $opscount{$Key}[$ops10ms],
		 		 		 		 		 		 $opscount{$Key}[$ops100ms] + $opscount{$Key}[$ops250ms],
		 		 		 		 		 		 $opscount{$Key}[$ops500ms] + $opscount{$Key}[$ops1000ms],
		 		 		 		 		 		 $opscount{$Key}[$ops2000ms],
		 		 		 		 		 		 $opscount{$Key}[$opsgr2000ms],
		 		 		 		 		 		 $opscount{$Key}[$opslogged],
		 		 		 		 		 		 $opscount{$Key}[$opsdropped]);
		 		 }
		 		 #printf ("%7d : Total Operations\n\n", $totalops);
		 		 
		 		 #printf ("%7d : Operations <= 10ms\n", $ops10ms);
		 		 #printf ("%7d : 10ms   > Operations <= 100ms\n", $ops100ms);
		 		 #printf ("%7d : 100ms  > Operations <= 250ms\n", $ops250ms);
		 		 #printf ("%7d : 250ms  > Operations <= 500ms\n", $ops500ms);
		 		 #printf ("%7d : 500ms  > Operations <= 1000ms\n", $ops1000ms);
		 		 #printf ("%7d : 1000ms > Operations <= 2000ms\t(logged)\n", $ops2000ms);
		 		 #printf ("%7d : Operations > 2000ms\t\t(logged)\n\n", $opsgr2000ms);
		 		 
		 		 printf ("\n%7d : Search Operations >= $toomanyresults results\n\n", $opscount{'Total'}[$opstoomanyresults]);

		 		 #printf ("%7d : Operations Logged\n\n", $opslogged);

		 		 # Count ops actively being processed (already counted in logSummary)
		 		 printf ("%7d : Operations Active\n\n", $opsactive);
		 		 #printf ("%7d : Operations Dropped\n\n", $opsdropped);

		 		 for ($i = 0; $i <= 80; $i++) {
		 		 		 if ($allerr[$i] != 0) {
		 		 #		 		 print "LDAP Code $i ($allmsg[$i]) : $allerr[$i] \n";
		 		 		 		 printf ("%7d : LDAP Code %2d (%s)\n", $allerr[$i], $i, $allmsg[$i]);
		 		 		 }
		 		 }
		 		 # Output start/end times for summary
		 		 printf "\nCurrent time: " . time2iso(time)      . " (local time)\n";
		 		 printf "Log started:  " . time2iso($startlog) . "\n";
		 		 printf "Log end:      " . time2iso($endlog)   . "\n\n";
		 }
}

# ===============================================================================
# logSummary
# ===============================================================================
sub logSummary
{
		 open(SUMMARY, "> $SUMMARYfile");
		 printf SUMMARY ("\n\n");
		 printf SUMMARY ("     Operation:  Count:<=0.010:<=0.250:<=1.000:<=2.000: >2.000: logged:dropped\n");
		 while( ($Key, $Value) = each(%opscount) ) {
		 		 printf SUMMARY ("%14s:%7d:%7d:%7d:%7d:%7d:%7d:%7d:%7d\n",
		 		 		 		 		 $Key,
		 		 		 		 		 $opscount{$Key}[$opstotal],
		 		 		 		 		 $opscount{$Key}[$ops10ms],
		 		 		 		 		 $opscount{$Key}[$ops100ms] + $opscount{$Key}[$ops250ms],
		 		 		 		 		 $opscount{$Key}[$ops500ms] + $opscount{$Key}[$ops1000ms],
		 		 		 		 		 $opscount{$Key}[$ops2000ms],
		 		 		 		 		 $opscount{$Key}[$opsgr2000ms],
		 		 		 		 		 $opscount{$Key}[$opslogged],
		 		 		 		 		 $opscount{$Key}[$opsdropped]);
		 }
		 #printf SUMMARY ("%7d : Total Operations\n\n", $totalops);
		 
		 #printf SUMMARY ("%7d : Operations <= 10ms\n", $ops10ms);
		 #printf SUMMARY ("%7d : 10ms   > Operations <= 100ms\n", $ops100ms);
		 #printf SUMMARY ("%7d : 100ms  > Operations <= 250ms\n", $ops250ms);
		 #printf SUMMARY ("%7d : 250ms  > Operations <= 500ms\n", $ops500ms);
		 #printf SUMMARY ("%7d : 500ms  > Operations <= 1000ms\n", $ops1000ms);
		 #printf SUMMARY ("%7d : 1000ms > Operations <= 2000ms\t(logged)\n", $ops2000ms);
		 #printf SUMMARY ("%7d : Operations > 2000ms\t\t(logged)\n\n", $opsgr2000ms);
		 
		 printf SUMMARY ("\n%7d : Search Operations >= $toomanyresults results\n\n", $opscount{'Total'}[$opstoomanyresults]);

		 #printf SUMMARY ("%7d : Operations Logged\n\n", $opslogged);

		 # Count ops actively being processed
		 $opsactive=0;
		 foreach $key (keys(%ops)) { $opsactive += 1; }
		 printf SUMMARY ("%7d : Operations Active\n\n", $opsactive);
		 #printf SUMMARY ("%7d : Operations Dropped\n\n", $opsdropped);
		 
		 for ($i = 0; $i <= 80; $i++) {
		 		 if ($allerr[$i] != 0) {
		 #		 		 print SUMMARY "LDAP Code $i ($allmsg[$i]) : $allerr[$i] \n";
		 		 		 printf SUMMARY ("%7d : LDAP Code %2d (%s)\n", $allerr[$i], $i, $allmsg[$i]) 
		 		 }
		 }
		 # Output start/end times for summary
		 printf SUMMARY "\nCurrent time: " . time2iso(time)      . " (local time)\n";
		 printf SUMMARY "Log started:  " . time2iso($startlog) . "\n";
		 printf SUMMARY "Log end:      " . time2iso($endlog)   . "\n\n";
		 close(SUMMARY);
}

# ===============================================================================
# debugOps
# ===============================================================================
sub debugOps
{
		 # for debugging, output any connections that were not seen to complete
		 #open(DEBUG, "> $DEBUGfile");
		 printf DEBUG ("\n\n");
		 while( ($key) = each(%ops) ) {
		 		 printf DEBUG "key=$key ";
		 		 #if ( $silent == 0 ) { printf "key=$key "; }
		 		 foreach $i (0..10) {
		 		 		 if ( $i == $starttime ) {
		 		 		 		 printf DEBUG "$i=" . time2iso($ops{$key}[$i]) . " ";
		 		 		 }
		 		 		 else {
		 		 		 		 printf DEBUG "$i=$ops{$key}[$i] ";
		 		 		 		 		 		 		 }
		 		 		 #if ( $silent == 0 ) { printf "$i=$ops{$key}[$i] "; }
		 		 }
		 		 printf DEBUG "\n";
		 		 #if ( $silent == 0 ) { printf "\n\n"; }
		 }
		 #close(DEBUG);
}

# ===============================================================================
# cleanOps
# ===============================================================================
sub cleanOps
{
		 # Check outstanding operations against timeout; if they are too old,
		 # log them and drop out of memory
		 foreach (keys %ops) {
		 		 if ( defined($ops{$_}[$starttime]) && ($timeval_last - $ops{$_}[$starttime] >= $timeout ) ) {
		 		 		 # The operation has timed out, log it
		 		 		 #print "looks like we need to drop opid=$_\n";
		 		 		 if ( $ops{$_}[$starttime] ne "unknown" ) {
		 		 		 		 $ops{$_}[$optime] = $timeout;
		 		 		 		 $ops{$_}[$starttime] = time2iso($ops{$_}[$starttime]);
		 		 		 }
		 		 		 $ops{$_}[$result] = "SCRIPT TIMEOUT";
		 		 		 $opscount{'Total'}[$opstotal] += 1;
		 		 		 $opscount{'Total'}[$opslogged] += 1;
		 		 		 $opscount{$ops{$_}[$optype]}[$opsdropped] += 1;
		 		 		 $opscount{$ops{$_}[$optype]}[$opslogged] += 1;
		 		 		 printf LOG "%s;%s:%s;%s;%s;%s;%.3f;%s;%s\n",
		 		 		 		 		 $ops{$_}[$starttime],
		 		 		 		 		 $ops{$_}[$connip],
		 		 		 		 		 $ops{$_}[$socket],
		 		 		 		 		 $ops{$_}[$connection],
		 		 		 		 		 $ops{$_}[$optype],
		 		 		 		 		 $ops{$_}[$result],
		 		 		 		 		 $ops{$_}[$optime],
		 		 		 		 		 $ops{$_}[$resultcount],
		 		 		 		 		 #$binds{$ops{$_}[$connection]},
		 		 		 		 		 $ops{$_}[$msgs];
		 		 		 # We're done with this connection, remove all references to it
		 		 		 delete($ops{$_});
		 		 }
		 }
}

# ===============================================================================
# findOp
# ===============================================================================
sub findOp
{
		 # If we don't have a parent operation to attach the message
		 # to, check the active ops for a match and use that key
		 # for the new message.
		 $found = 0;
		 # We have nothing, so aggressively match
		 if ( ($connid eq "unknown") && ($op =~ /unknown|0x0000:0x00|0x0000:0x77/) ) {
		 		 foreach (keys %ops) {
		 		 		 # Match the connection
		 		 		 if ( defined($ops{$_}[$connection]) && ($connid eq $ops{$_}[$connection]) ) {
		 		 		 		 #print "$opid hit on connection! line=$line found=$_\n"; $dummy = <STDIN>;
		 		 		 		 $connid = $ops{$_}[$connection]; $op = $ops{$_}[$opno];
		 		 		 		 $ipaddr = $ops{$_}[$connip]; $sock = $ops{$_}[$socket];
		 		 		 		 $opid = $_;
		 		 		 		 $found = 1;
		 		 		 		 last;
		 		 		 }
		 		 		 # Match the connip, socket, and opno
		 		 		 elsif ( defined($ops{$_}[$connip]) && defined($ops{$_}[$socket]) && defined($ops{$_}[$opno]) && ($ipaddr eq $ops{$_}[$connip]) && ($sock eq $ops{$_}[$socket]) && ($op eq $ops{$_}[$opno]) ) {
		 		 		 		 #print "$opid $ipaddr:$sock matches\n$_ $ops{$_}[$connip]:$ops{$_}[$socket]\nline=$line\n"; $dummy = <STDIN>;
		 		 		 		 $connid = $ops{$_}[$connection]; $op = $ops{$_}[$opno];
		 		 		 		 $ipaddr = $ops{$_}[$connip]; $sock = $ops{$_}[$socket];
		 		 		 		 $opid = $_;
		 		 		 		 $found = 1;
		 		 		 		 last;
		 		 		 }
		 		 		 #if ( $_ =~ /$ipaddr:$sock/ ) {
		 		 		 #		 #print "$opid hit on ipaddr:socket! found=$_\n";
		 		 		 #		 $opid = $_;
		 		 		 #		 last;
		 		 		 #}
		 		 		 #elsif ( $opthrd =~ /$ops{$_}[$thread]/ ) {
		 		 		 #		 #print "$opid hit on thread! found=$_\n";
		 		 		 #		 $opid = $_;
		 		 		 #		 last;
		 		 		 #}
		 		 }
		 }
		 # We have an operation number, but no connection ID; carefully match
		 # and use the found connection ID
		 elsif ( ($connid eq "unknown") && ($op ne "unknown") && ($op ne "0x0000:0x00") && ($op ne "0x0000:0x77") ) {
		 		 foreach (keys %ops) {
		 		 		 # Match the connip, socket, and opno
		 		 		 if ( defined($ops{$_}[$connip]) && defined($ops{$_}[$socket]) && defined($ops{$_}[$opno]) && ($ipaddr eq $ops{$_}[$connip]) && ($sock eq $ops{$_}[$socket]) && ($op eq $ops{$_}[$opno]) ) {
		 		 		 		 #print "$opid $ipaddr:$sock matches\n$_ $ops{$_}[$connip]:$ops{$_}[$socket]\nline=$line\n"; $dummy = <STDIN>;
		 		 		 		 $connid = $ops{$_}[$connection]; $op = $ops{$_}[$opno];
		 		 		 		 $ipaddr = $ops{$_}[$connip]; $sock = $ops{$_}[$socket];
		 		 		 		 $opid = $_;
		 		 		 		 $found = 1;
		 		 		 		 last;
		 		 		 }
		 		 }
		 }
		 # We have a connection ID, but no operation number; carefully match
		 # and use the found $op, $ipaddr, and $sock
		 elsif ( ($connid ne "unknown") && ($op =~ /unknown|0x0000:0x00|0x0000:0x77/) ) {
		 		 foreach (keys %ops) {
		 		 		 # Match the connection
		 		 		 if ( defined($ops{$_}[$connection]) && ($connid eq $ops{$_}[$connection]) ) {
		 		 		 		 #print "$opid $ipaddr:$sock matches\n$_ $ops{$_}[$connip]:$ops{$_}[$socket]\nline=$line\n"; $dummy = <STDIN>;
		 		 		 		 $connid = $ops{$_}[$connection]; $op = $ops{$_}[$opno];
		 		 		 		 $ipaddr = $ops{$_}[$connip]; $sock = $ops{$_}[$socket];
		 		 		 		 $opid = $_;
		 		 		 		 $found = 1;
		 		 		 		 last;
		 		 		 }
		 		 }
		 }
		 #if ( $found == 0 ) {
		 #		 print "No match found for $opid $ipaddr:$sock\nline=$line\n"; $dummy = <STDIN>;
		 #}
}

# ===============================================================================
# checkOp
# ===============================================================================
sub checkOp
{
		 my ($opid,$line) = @_;
		 # If any values are empty or unknown, use the values found on current line
		 $ops{$opid}[$starttime] = $timeval		 unless ( (defined($ops{$opid}[$starttime])) && ($ops{$opid}[$starttime] != 0) );
		 $ops{$opid}[$thread] = $opthrd 		 unless ( (defined($ops{$opid}[$thread])) && ($ops{$opid}[$thread] ne "unknown") );
		 $ops{$opid}[$connip] = $ipaddr		 unless ( (defined($ops{$opid}[$connip])) && ($ops{$opid}[$connip] ne "unknown") );
		 $ops{$opid}[$socket] = $sock		 		 unless ( (defined($ops{$opid}[$socket])) && ($ops{$opid}[$socket] ne "unknown") );
		 $ops{$opid}[$opno] = $op		 		 unless ( (defined($ops{$opid}[$opno])) && ($ops{$opid}[$opno] ne "unknown") );
		 $ops{$opid}[$connection] = $connid		 unless ( (defined($ops{$opid}[$connection])) && ($ops{$opid}[$connection] ne "unknown") );
		 $ops{$opid}[$optype] = $operation		 unless ( (defined($ops{$opid}[$optype])) && ($ops{$opid}[$optype] ne "unknown") );
		 $ops{$opid}[$result] = "unknown"		 unless ( (defined($ops{$opid}[$result])) );
		 $ops{$opid}[$optime] = 999		 		 unless ( (defined($ops{$opid}[$optime])) );
		 $ops{$opid}[$resultcount] = 0		 		 unless ( (defined($ops{$opid}[$resultcount])) );
		 if ( defined($ops{$opid}[$msgs]) ) { $ops{$opid}[$msgs] = $ops{$opid}[$msgs] . $line . "#"; }
		 else { $ops{$opid}[$msgs] = $line . "#"; }
}

# ===============================================================================
# tallyOp
# ===============================================================================
sub tallyOp
{
		 my ($opid) = @_;
		 # Calculate operation duration (seconds)
		 if ( $ops{$opid}[$starttime] ne "unknown" ) {
		 		 $ops{$opid}[$optime] = $timeval - $ops{$opid}[$starttime];
		 		 #print "$timeval - $ops{$opid}[$starttime] = $ops{$opid}[$optime]\n";
		 		 $ops{$opid}[$starttime] = time2iso($ops{$opid}[$starttime]);
		 }
		 # If this is the first of this optype, intialize the counter
		 unless ( defined($opscount{$ops{$opid}[$optype]}) ) {
		 		 foreach $i (0..10) { $opscount{$ops{$opid}[$optype]}[$i]=0; }
		 }
		 $opscount{'Total'}[$opstotal] += 1;
		 $opscount{$ops{$opid}[$optype]}[$opstotal] += 1;
		 $opscount{$ops{$opid}[$optype]}['Total'] += 1 if ( $ops{$opid}[$optype] eq "unknown" );
		 # Classify how long this operation took, and log if excessive
		 if ( $ops{$opid}[$optime] <= 0.010 ) {
		 		 $opscount{$ops{$opid}[$optype]}[$ops10ms] += 1;
		 		 $opscount{'Total'}[$ops10ms] += 1;
		 }
		 elsif ( $ops{$opid}[$optime] <= 0.100 ) {
		 		 $opscount{$ops{$opid}[$optype]}[$ops100ms] += 1;
		 		 $opscount{'Total'}[$ops100ms] += 1;
		 }
		 elsif ( $ops{$opid}[$optime] <= 0.250 ) {
		 		 $opscount{$ops{$opid}[$optype]}[$ops250ms] += 1;
		 		 $opscount{'Total'}[$ops250ms] += 1;
		 }
		 elsif ( $ops{$opid}[$optime] <= 0.500 ) {
		 		 $opscount{$ops{$opid}[$optype]}[$ops500ms] += 1;
		 		 $opscount{'Total'}[$ops500ms] += 1;
		 }
		 elsif ( $ops{$opid}[$optime] <= 1.000 ) {
		 		 $opscount{$ops{$opid}[$optype]}[$ops1000ms] += 1;
		 		 $opscount{'Total'}[$ops1000ms] += 1;
		 }
		 elsif ( $ops{$opid}[$optime] <= 2.000 ) {
		 		 $opscount{$ops{$opid}[$optype]}[$ops2000ms] += 1;
		 		 $opscount{'Total'}[$ops2000ms] += 1;
		 		 $output=1;
		 }
		 elsif ( $ops{$opid}[$optime] > 2.000 ) {
		 		 $opscount{$ops{$opid}[$optype]}[$opsgr2000ms] += 1;
		 		 $opscount{'Total'}[$opsgr2000ms] += 1;
		 		 $output=1;
		 }
		 # Log operations that return lots of results
		 if ( $ops{$opid}[$resultcount] >= $toomanyresults ) {
		 		 $opscount{$ops{$opid}[$optype]}[$opstoomanyresults] += 1;
		 		 $opscount{'Total'}[$opstoomanyresults] += 1;
		 		 $output = 1;
		 }
		 if ( $output == 1 ) {
		 		 $opscount{$ops{$opid}[$optype]}[$opslogged] += 1;
		 		 $opscount{'Total'}[$opslogged] += 1;
		 		 printf LOG "%s;%s:%s;%s;%s;%s;%.3f;%s;%s\n",
		 		 		 		 $ops{$opid}[$starttime],
		 		 		 		 $ops{$opid}[$connip],
		 		 		 		 $ops{$opid}[$socket],
		 		 		 		 $ops{$opid}[$connection],
		 		 		 		 $ops{$opid}[$optype],
		 		 		 		 $ops{$opid}[$result],
		 		 		 		 $ops{$opid}[$optime],
		 		 		 		 $ops{$opid}[$resultcount],
		 		 		 		 #$binds{$ops{$opid}[$connection]},
		 		 		 		 $ops{$opid}[$msgs];
		 }
		 # for debugging each operation loop
		 #while( ($key,$val) = each(%ops) ) {
		 #		 printf ("totalops=$totalops key=$key ");
		 #		 foreach $i (0..10) {
		 #		 		 printf ("$i=$ops{$key}[$i] ");
		 #		 }
		 #		 printf "\n\n";
		 #}
		 # We're done with this connection, remove all references to it
		 delete($ops{$opid});
		 #$opthrd = "unknown";
		 #$timeval = 0;
		 #$ipaddr = "unknown"; $sock = "unknown";
		 #$op = "unknown"; $opid = "unknown"; $connid = "unknown";
		 #$operation = "unknown";
}

# ===============================================================================
# connClosed
# ===============================================================================
sub connClosed
{
		 my ($connid,$line) = @_;
		 foreach (keys %ops) {
		 		 if ( $connid eq $ops{$_}[$connection] ) {
		 		 		 &checkOp($_,$line);
		 		 		 &tallyOp($_);
		 		 }
		 }
}

# ===============================================================================
# parseLine
# ===============================================================================
sub parseLine
{
	 my ($line) = @_;
	 # Check last $refreshTimer value; if more than $refresh seconds have elapsed,
	 # write out summary information
	 if ( (time - $refreshTimer) >= $refresh ) {
	 		 logSummary;
	 		 &displaySummary;
	 		 $refreshTimer=time;
	 		 $refreshCounter += 1;
	 		 if ( $refreshCounter >= $cleanupCount ) { cleanOps; $refreshCounter = 0; }
	 }
	 # Keep track of previous operation key values
	 $opthrd_last = $opthrd; $opid_last = $opid; $connid_last = $connid; $sock_last = $sock;
	 $timeval_last = $timeval if $timeval != 0;
	 # Reinit temp values
	 $opthrd = "unknown";
	 $timeval = 0;
	 $ipaddr = "unknown"; $sock = "unknown";
	 $op = "unknown"; $opid = "unknown"; $connid = "unknown";
	 $operation = "unknown";
	 $output=$outputdef;
	 chomp $line;
	 if ($line =~ /^\t/) {
	 		 $opid = $opid_last;
	 		 $ops{$opid_last}[$msgs] = $ops{$opid_last}[$msgs] . $line . "#";
	 }
	 elsif ( $line =~ /Work info status|Thread pool status|Checking for configuration changes/ ) { } # Drop these lines
	 elsif ( $line =~ /LDAP:\s+/ ) {
		 if ( $line =~ /(\d{1,6}) LDAP:\s+(.*)/ ) {
			$opthrd = $1;
			$line = $2;
		 }
		 elsif ( $line =~ /^LDAP:\s+(.*)/ ) {
		 	$opthrd = "9999"; # NetWare doesn't provide an opthrd like other platforms
		 	$line = $1;
		 }
		 else {
		 	print "WARNING: found unrecognized message... too many NDSTRACE filters?\n";
			#print "WARN: line=$line\n"; $dummy = <STDIN>;
		 	return;
		 }
 		 #print "PASS1: opthrd=$opthrd line=$line\n";
 		 # Get the date and time from the line
 		 if ( $line =~ /\[(\d{4}\/\d\d\/\d\d)\s+(\d{1,2}\:\d\d\:\d\d)(\.\d{1,3})\] (.*)/ ) {
 		 		 #print "found ms 1=$1 2=$2 3=$3\n";
 		 		 $date = $1;
 		 		 $time = $2;
 		 		 #if ( !($date) || !($time) ) {
 		 		 #		 #print "busted line=$line\n";
 		 		 #		 next;
 		 		 #}
 		 		 $ms = $3;
 		 		 # Drop the portion of the string already interpreted
 		 		 $line = $4;
 		 }
 		 # Handle older logs that don't provide miliseconds
 		 elsif ( $line =~ /\[(\d{4}\/\d\d\/\d\d)\s+(\d{1,2}\:\d\d\:\d\d)\] (.*)/ ) {
 		 		 #print "found NO ms 1=$1 2=$2\n";
 		 		 $date = $1;
 		 		 $time = $2;
 		 		 #if ( !($date) || !($time) ) {
 		 		 #		 print "busted line=$line\n";
 		 		 #		 next;
 		 		 #}
 		 		 $ms = 0;
 		 		 # Drop the portion of the string already interpreted
 		 		 $line = $3;
 		 }
 		 # Fix issue where ms are not properly 0 padded; e.g. 1040ms will
 		 # end up like this:  1.40 rather than 1.040.
 		 if ( $ms =~ /\.(\d{1,3})/ ) { $ms = ($1/1000); }
 		 # Convert date/time to machine time (seconds since 1/1/1970 00:00)
 		 $timeval = str2time("$date $time") + $ms;
 		 $startlog = $timeval		 unless ( $startlog != 0 );
 		 $endlog = $timeval;
 		 #print "PASS2: timeval=$timeval ms=$ms line=$line\n";
 		 # Get the IP address and connection ID from the line
 		 if ( $line =~ /\((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d*)\)\((0x[a-fA-F0-9]{4,8}:0x[a-fA-F0-9]{2})\) (.*)/ ) {
 		 		 $ipaddr = $1 if defined($1);
 		 		 $sock = $2 if defined($2);
 		 		 $op = $3 if defined($3);
 		 		 # Drop the portion of the string already interpreted
 		 		 $line = $4;
 		 }
 		 #print "PASS3: ipaddr=$ipaddr sock=$sock line=$line\n";
 		 # Handle TLS operation oddities
 		 if ( $op =~ /0x0000:0x77/ ) { $op = "0x0000:0x00"; }
 		 # Get the connection number, if present
 		 if ( $line =~ /[Cc]onnection (0x[a-fA-F0-9]{1,8})/ ) { $connid = $1; }
 		 # Try to find the correct ip and/or sock if it hasn't been found yet
 		 if ( ($ipaddr eq "unknown") || ($sock eq "unknown") ) {
 		 		 if ( $line =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d*)/ ) {
 		 		 		 $ipaddr = $1 if defined($1);
 		 		 		 $sock = $2 if defined($2);
 		 		 }
 		 }
 		 $opid = "$connid:$op";
 		 #print "PASS4: ipaddr:sock:op=$ipaddr:$sock:$op line=$line\n"; $dummy = <STDIN>;
 		 # If we have only partial information for the opid, try to link it with another active op
 		 #if ( ($ipaddr eq "unknown") || ($sock eq "unknown") || ($op =~ /unknown|0x0000:0x00|0x0000:0x77/) ) {
 		 if ( ($connid eq "unknown") || ($op =~ /unknown|0x0000:0x00|0x0000:0x77/) ) {
 		 		 #print "connid=$connid op=$op ipaddr=$ipaddr sock=$sock line=$line\n";
 		 		 &findOp;
 		 }
 		 # Now we starting building/updating the hash for this op
 		 if ( $line =~ /(DoBind|DoUnbind|DoAbandon|DoTLSHandshake|DoSearch|DoCompare|DoAdd|DoModify|DoModifyDN|DoDelete|DoExtended) on connection 0x[a-fA-F0-9]{1,8}/ ) {
 		 		 $operation=$1;
 		 		 # Find out if this should be a new record or go onto an existing one
 		 		 if ( (defined($ops{$opid}[$optype])) && ($ops{$opid}[$optype] ne "unknown") ) {
 		 		 		 if ( $ops{$opid}[$msgs] =~ /Activating pending operation/ ) {
 		 		 		 		 # This op is a continuance of previously delayed op
 		 		 		 }
 		 		 		 else {
 		 		 		 		 # This is a new op, so let's log the previous one and start a
 		 		 		 		 # new record
 		 		 		 		 &tallyOp($opid);
 		 		 		 }
 		 		 }
 		 		 #print "operation=$operation line=$line\n" if !(defined($operation));
 		 		 # If this is the first of this optype, intialize the counter
 		 		 unless ( defined($opscount{$operation}) ) {
 		 		 		 foreach $i (0..10) { $opscount{$operation}[$i]=0; }
 		 		 }
 		 		 #&checkOp($opid,$line);
 		 		 #print "$1=",$opscount['$1'],"\n";
 		 		 # Use values from current op unless we already have values from an op continuance
 		 		 # NOTE:  This is just like &checkOp, but it doesn't accumulate contents of the
 		 		 #        current line into $ops{$opid}[$msgs]
 		 		 $ops{$opid}[$starttime] = $timeval		 unless ( (defined($ops{$opid}[$starttime])) && ($ops{$opid}[$starttime] != 0) );
 		 		 $ops{$opid}[$thread] = $opthrd 		 unless ( (defined($ops{$opid}[$thread])) && ($ops{$opid}[$thread] ne "unknown") );
 		 		 $ops{$opid}[$connip] = $ipaddr		 unless ( (defined($ops{$opid}[$connip])) && ($ops{$opid}[$connip] ne "unknown") );
 		 		 $ops{$opid}[$socket] = $sock		 		 unless ( (defined($ops{$opid}[$socket])) && ($ops{$opid}[$socket] ne "unknown") );
 		 		 $ops{$opid}[$opno] = $op		 		 unless ( (defined($ops{$opid}[$opno])) && ($ops{$opid}[$opno] ne "unknown") );
 		 		 $ops{$opid}[$connection] = $connid		 unless ( (defined($ops{$opid}[$connection])) && ($ops{$opid}[$connection] ne "unknown") );
 		 		 $ops{$opid}[$optype] = $operation		 unless ( (defined($ops{$opid}[$optype])) && ($ops{$opid}[$optype] ne "unknown") );
 		 		 $ops{$opid}[$result] = "unknown"		 unless ( (defined($ops{$opid}[$result])) );
 		 		 $ops{$opid}[$optime] = 999		 		 unless ( (defined($ops{$opid}[$optime])) );
 		 		 $ops{$opid}[$resultcount] = 0		 		 unless ( (defined($ops{$opid}[$resultcount])) );
 		 }
 		 # Reassociate pending op to the original when reactivated
 		 elsif ( $line =~ /Activating pending operation 0x([a-fA-F0-9]{1,8}):0x([a-fA-F0-9]{2}) on connection (0x[a-fA-F0-9]{1,8})/ ) {
 		 		 $op = sprintf("0x%04x:0x%02x",hex($1),hex($2));
 		 		 #print "1=$1 2=$2 newop=$op\n"; $dummy = <STDIN>;
 		 		 $opid = "$connid:$op";
 		 		 &checkOp($opid,$line);
 		 		 #print "opid=$opid msgs=$ops{$opid}[$msgs]\n"; $dummy = <STDIN>;
 		 }
 		 # Special handling again for TLS
 		 elsif ( $line =~ /New TLS connection/ ) {
 		 		 # If there was a previously recognized operation on this opid
 		 		 # flush it so we can start tracking a new op
 		 		 if ( (defined($ops{$opid}[$optype])) && ($ops{$opid}[$optype] ne "unknown") ) { &tallyOp($opid); }
 		 		 # Combine TLS op messages together
 		 		 #if ( $operation =~ /DoTLSHandshake/ ) {
 		 		 #		 if ( $connid =~ $ops{'unknown'}[$connection] ) {
 		 		 #		 		 $ops{$opid}[$msgs] = $ops{'unknown'}[$msgs] . "#";
 		 		 #		 		 delete($ops{'unknown'});
 		 		 #		 }
 		 		 #}
 		 		 # Don't try to match this up to an existing op, just move on
 		 		 &checkOp($opid,$line);
 		 }
 		 # If this is a search result, add to the result counter
 		 elsif ( $line =~ /Sending search result entry/ ) {
 		 		 #&findOp		 		 unless ( defined($ops{$opid}[$optype]) && ($ops{$opid}[$optype] ne "unknown") );
 		 		 if ( defined($ops{$opid}[$resultcount]) ) { $ops{$opid}[$resultcount] += 1; }
 		 		 else { $ops{$opid}[$resultcount] = 1; }
 		 }
 		 # Look for LDAP result codes first
 		 elsif ( $line =~ /operation result/ ) {
 		 		 #&findOp		 		 unless ( defined($ops{$opid}[$optype]) && ($ops{$opid}[$optype] ne "unknown") );
 		 		 @fields = split (/result /, $line);
 		 		 @code = split (/:/, $fields[1]);
 		 		 $ldaperr = $code[0];
 		 		 #if ( $ldaperr != 0 ) { print "connid=$connid ldaperr=$ldaperr\n"; }
 		 		 $currentcnt = $allerr[$ldaperr];
 		 		 $newcnt = $currentcnt + 1;
 		 		 $allerr[$ldaperr] =  $newcnt;
 		 		 # Get friendly error description for the log
 		 		 $ops{$opid}[$result] = $allmsg[$ldaperr];
 		 		 # Log operations with abnormal result codes
 		 		 unless ( $ldaperr == 0 || $ldaperr == 5 || $ldaperr == 6 || $ldaperr == 49 || $ldaperr == 65 ) {
 		 		 		 $output = 1;		 # Don't output the "normal" result ops
 		 		 }
 		 		 &checkOp($opid,$line);
 		 		 unless ( $ldaperr == 1 || $ldaperr == 2 ) {
 		 		 		 # All but LDAP results 1 and 2 can be wrapped up now
 		 		 		 &tallyOp($opid);
 		 		 }
 		 }
 		 # Look for other messages that logically complete an "operation"
 		 elsif ( $line =~ /Completed TLS handshake|Abandon.*on connection 0x[a-fA-F0-9]{1,8}/ ) {
 		 #elsif ( $line =~ /Completed TLS handshake|Monitor 0x.. found connection 0x[a-fA-F0-9]{1,8} socket failure|Abandon.*on connection 0x[a-fA-F0-9]{1,8}/ ) {
 		 #elsif ( $line =~ /Connection 0x[a-fA-F0-9]{1,8} closed|Completed TLS handshake|Monitor 0x.. found connection 0x[a-fA-F0-9]{1,8} socket failure|Server closing connection 0x[a-fA-F0-9]{1,8}|Abandon.*on connection 0x[a-fA-F0-9]{1,8}/ ) {
 		 #elsif ( $line =~ /Connection 0x[\dabcdef]{1,8} closed|Completed TLS handshake|Monitor 0x.. found connection 0x[\dabcdef]{1,8} socket failure|Server closing connection 0x[\dabcdef]{1,8}|DoUnbind on connection 0x[\dabcdef]{1,8}|Abandon.*on connection 0x[\dabcdef]{1,8}/ ) {
 		 #elsif ( $line =~ /Connection 0x[\dabcdef]{1,8} closed|Completed TLS handshake|Monitor 0x.. found connection 0x[\dabcdef]{1,8} socket failure|Monitor 0x.. initiating TLS handshake on connection 0x[\dabcdef]{1,8}|Server closing connection 0x[\dabcdef]{1,8}/ )
 		 		 #&findOp		 		 unless ( defined($ops{$opid}[$optype]) && ($ops{$opid}[$optype] ne "unknown") );
 		 		 &checkOp($opid,$line);
 		 		 # If result is still 'unknown', give it a more useful value
 		 		 #$ops{$opid}[$result] = "MISC"		 		 unless ( defined($ops{$opid}[$result]) && ($ops{$opid}[$result] ne "unknown") );
 		 		 &tallyOp($opid);
 		 }
 		 # This is a message that cannot be related to any other op; just log it
 		 elsif ( $line =~ /Listener new socket accept failed/ ) {
 		 		 &checkOp($opid,$line);
 		 		 $ops{$opid}[$result] = "RESET";
 		 		 &tallyOp($opid);
 		 }
 		 # Forcing abandon, wrap up any related op
 		 elsif ( $line =~ /Forcing abandon on operation 0x([a-fA-F0-9]{1,8}):0x([a-fA-F0-9]{2}) on connection (0x[a-fA-F0-9]{1,8})/ ) {
 		 		 $op = sprintf("0x%04x:0x%02x",hex($1),hex($2));
 		 		 #print "1=$1 2=$2 newop=$op\n"; $dummy = <STDIN>;
 		 		 $opid = "$connid:$op";
 		 		 #print "opid=$opid line=$line msgs=$ops{$opid}[$msgs]\n"; $dummy = <STDIN>;
 		 		 if ( defined($ops{$opid}) ) {
 		 		 		 &checkOp($opid,$line);
 		 		 		 # If result is still 'unknown', give it a more useful value
 		 		 		 #$ops{$opid}[$result] = "ABANDON"		 unless ( defined($ops{$opid}[$result]) && ($ops{$opid}[$result] ne "unknown") );
 		 		 		 # This op has been abandoned... no more messages
#		 		 		 		 &tallyOp($opid);
 		 		 }
 		 }
 		 # Reassociate preempted op
 		 elsif ( $line =~ /Preempting operation 0x([a-fA-F0-9]{1,8}):0x([a-fA-F0-9]{2}) on connection (0x[a-fA-F0-9]{1,8})/ ) {
 		 		 $op = sprintf("0x%04x:0x%02x",hex($1),hex($2));
 		 		 #print "1=$1 2=$2 newop=$op\n"; $dummy = <STDIN>;
 		 		 $opid = "$connid:$op";
 		 		 if ( defined($ops{$opid}) ) {
 		 		 		 &checkOp($opid,$line);
 		 		 		 # If result is still 'unknown', give it a more useful value
 		 		 		 #$ops{$opid}[$result] = "PREEMPT"		 unless ( defined($ops{$opid}[$result]) && ($ops{$opid}[$result] ne "unknown") );
 		 		 		 # This op has been abandoned... no more messages
#		 		 		 		 &tallyOp($opid);
 		 		 }
 		 }
 		 # This connection is closing -- wrap up all of the related operations
 		 elsif ( $line =~ /Connection 0x[a-fA-F0-9]{1,8} closed/ ) {
 		 		 &connClosed($connid,$line);
 		 }
 		 # Handle the messages that don't appear to explicitly mark the beginning
 		 # or end of an operation
 		 else {
 		 		 &checkOp($opid,$line);
 		 }
	 }
}